package utils

import (
	"errors"
	"fmt"
	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	"github.com/kataras/iris/v12"
	"github.com/mitchellh/mapstructure"
	"ir/models/structs"
	cnTranslations "ir/utils/validate/translations/zh"
	"reflect"
	"strconv"
	"strings"
	"unicode/utf8"
)

type FormData struct {
	Ctx    iris.Context
	Data   map[string][]string
	Fields []string
	Values map[string]interface{}
	Mode   int // 0 insert 1 update
}

type FieldCheckResult struct {
	Field      string
	Value      string
	ValueInt   int
	ValueInt64 int64
	Exist      bool
	Err        error
}

func GetFormData(ctx iris.Context) FormData {
	return FormData{
		Ctx:  ctx,
		Data: ctx.FormValues(),
	}
}

func (fd *FormData) SetMode(mode int) {
	fd.Mode = mode
}

func (fd *FormData) Get(fieldName string) (value string, exist bool) {
	values, exist := fd.Data[fieldName]
	if exist {
		value = values[0]
	}
	return value, exist
}

func (fd *FormData) GetInt(fieldName string) (value int, exist bool) {
	values, has := fd.Data[fieldName]
	if has {
		sVal := values[0]
		iVal, err := strconv.Atoi(sVal)
		if err == nil {
			value = iVal
			exist = has
		}
	}
	return value, exist
}

func (fd *FormData) AddFields(fieldName ...string) {
	fd.Fields = append(fd.Fields, fieldName...)
}

func (fd *FormData) Tidy(validations []structs.Validation, data interface{}) error {
	values := make(map[string]interface{})
	for _, col := range validations {
		ckRes := fd.Validate(col)
		if ckRes.Err != nil {
			return ckRes.Err
		}
		if ckRes.Exist {
			field := ckRes.Field
			asField := field
			if col.As != "" {
				asField = col.As
			} else {
				iPos := strings.Index(field, "_")
				if iPos > 0 {
					asField = strings.Replace(field, "_", "", -1)
				}
			}
			if col.Type == "int" {
				values[asField] = ckRes.ValueInt
			} else if col.Type == "int64" {
				values[asField] = ckRes.ValueInt64
			} else {
				values[asField] = ckRes.Value
			}
			if !col.QueryKey {
				// 更新时，跳过不需要更新的字段
				if !(fd.Mode == 1 && col.NoUpdate) {
					fd.AddFields(field)
				}
			}
		}
	}
	fd.Values = values
	return mapstructure.Decode(values, &data)
}

func (fd *FormData) Validate(validation structs.Validation) (result FieldCheckResult) {
	field := validation.Field
	rules := validation.Rules
	sValue, exist := fd.Get(field)
	result.Field = field
	result.Value = sValue
	result.Exist = exist
	if exist {
		if validation.Type != "" {
			validTypes := []string{"int", "int64"}
			if isValid, _ := InArray(validation.Type, validTypes); isValid {
				if validation.Type == "int" {
					result.ValueInt, result.Err = strconv.Atoi(sValue)
					if validation.QueryKey && result.ValueInt > 0 {
						fd.Mode = 1
					}
					if result.Err != nil {
						return
					}
				} else if validation.Type == "int64" {
					result.ValueInt64, result.Err = strconv.ParseInt(sValue, 10, 64)
					if validation.QueryKey && result.ValueInt64 > 0 {
						fd.Mode = 1
					}
					if result.Err != nil {
						return
					}
				}
			} else {
				result.Err = errors.New("不支持的字段类型" + validation.Type)
				return
			}
		} else if validation.QueryKey {
			if sValue != "" {
				fd.Mode = 1
			}
		}
	}
	for _, rule := range rules {
		if "required" == rule {
			if fd.Mode == 0 {
				if exist == false {
					result.Err = fmt.Errorf("%s is a required field", field)
					break
				}
			}
		} else if "numeric" == rule {
			if exist {
				if IsNumeric(sValue) == false {
					result.Err = fmt.Errorf("%s must be a number", field)
					break
				}
			}
		} else if strings.Contains(rule, "=") {
			if exist {
				arr := strings.Split(rule, "=")
				if len(arr) == 2 {
					flag := arr[0]
					if flag == "min" {
						iMin, aErr := strconv.Atoi(arr[1])
						if aErr != nil {
							result.Err = aErr
							break
						}
						if !(iMin >= 0) {
							result.Err = fmt.Errorf("%s validation conditions (min value) is invalid", field)
							break
						}
						if validation.Type == "" {
							result.ValueInt, result.Err = strconv.Atoi(sValue)
							if result.Err != nil {
								return
							}
						}
						if result.ValueInt < iMin {
							result.Err = fmt.Errorf("%s must be %d or less", field, iMin)
							break
						}
					} else if flag == "max" {
						iMax, aErr := strconv.Atoi(arr[1])
						if aErr != nil {
							result.Err = aErr
							break
						}
						if !(iMax >= 0) {
							result.Err = fmt.Errorf("%s validation conditions (max value) is invalid", field)
							break
						}
						if validation.Type == "" {
							result.ValueInt, result.Err = strconv.Atoi(sValue)
							if result.Err != nil {
								return
							}
						}
						if result.ValueInt > iMax {
							result.Err = fmt.Errorf("%s must be %d or greater", field, iMax)
							break
						}
					} else if flag == "minLength" {
						iMinLength, aErr := strconv.Atoi(arr[1])
						if aErr != nil {
							result.Err = aErr
							break
						}
						if !(iMinLength > 0) {
							result.Err = fmt.Errorf("%s validation conditions (min length) is invalid", field)
							break
						}
						strLen := utf8.RuneCountInString(sValue)
						if strLen < iMinLength {
							result.Err = fmt.Errorf("%s must be at least %d in length", field, iMinLength)
							break
						}
					} else if flag == "maxLength" {
						iMaxLength, aErr := strconv.Atoi(arr[1])
						if aErr != nil {
							result.Err = aErr
							break
						}
						if !(iMaxLength > 0) {
							result.Err = fmt.Errorf("%s validation conditions (max length) is invalid", field)
							break
						}
						strLen := utf8.RuneCountInString(sValue)
						if strLen > iMaxLength {
							result.Err = fmt.Errorf("%s must be a maximum of %d in length", field, iMaxLength)
							break
						}
					}
				} else {
					result.Err = fmt.Errorf("%s validation conditions is invalid", field)
					break
				}
			}
		} else {
			result.Err = fmt.Errorf("%s rule %s is invalid", field, rule)
			break
		}
	}
	return
}

func (fd *FormData) IsRequired(validations []structs.Validation) (value string, exist bool) {
	for _, rule := range validations {
		val, exist := fd.Get(rule.Field)
		if exist {
			fmt.Println(val)
		}
		fmt.Println(rule.Field)
	}
	return
}

func Validate(data interface{}) (et string) {
	en := en.New()
	cn := zh.New()
	uni := ut.New(en, cn)
	trans, _ := uni.GetTranslator("zh")
	validate := validator.New()
	//注册一个函数，获取struct tag里自定义的label作为字段名
	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
		name := fld.Tag.Get("label")
		return name
	})
	cnTranslations.RegisterDefaultTranslations(validate, trans)
	err := validate.Struct(data)
	if err != nil {
		errs := err.(validator.ValidationErrors)
		for _, e := range errs {
			et = e.Translate(trans)
			break
		}
	}
	return
}

func ValidateVariable(variable interface{}, tag string) (et string) {
	en := en.New()
	cn := zh.New()
	uni := ut.New(en, cn)
	trans, _ := uni.GetTranslator("zh")
	validate := validator.New()
	cnTranslations.RegisterDefaultTranslations(validate, trans)
	errs := validate.Var(variable, tag)
	if errs != nil {
		vErr := errs.(validator.ValidationErrors)
		for _, e := range vErr {
			et = e.Translate(trans)
			break
		}
	}
	return
}

type JsonForm struct {
	Ctx          iris.Context
	Data         map[string]interface{} // 转换成map的数据
	Fields       []string
	Mode         int // 0 insert 1 update
	InterfaceMap map[string]interface{}
}

func NewJsonForm(ctx iris.Context) JsonForm {
	var data map[string]interface{}
	ctx.ReadJSON(&data)
	return JsonForm{
		Ctx:          ctx,
		Data:         data,
		InterfaceMap: make(map[string]interface{}),
	}
}

func hasRequiredValidate(validate string) bool {
	validates := strings.Split(validate, ",")
	if has, _ := InArray("required", validates); has {
		return true
	}
	return false
}

func (jf *JsonForm) SetData(data interface{}) (err error) {
	var md map[string]interface{}
	var ok bool
	if md, ok = data.(map[string]interface{}); !ok {
		err = errors.New("数据格式错误")
		return
	}
	jf.Data = md
	return
}

// Pick 收集表内需要更新的字段
func (jf *JsonForm) Pick(validations []structs.Validation, entity interface{}) (err error) {
	fields := make([]string, 0)
	mapValues := make(map[string]interface{})
	// 判断字段里是否存在查询键，如果存在，则数据标记为更新
	for _, item := range validations {
		field := item.Field
		val := jf.Data[field]
		if val != nil && item.QueryKey {
			jf.Mode = 1
			break
		}
	}
	for _, item := range validations {
		field := item.Field
		// 提交字段存在时，则执行验证，否则在插入时，并且有"required"验证规则时验证。
		val := jf.Data[field]
		if val != nil {
			// 更新时，如有NoUpdate，不加到更新字段
			if jf.Mode == 1 && item.NoUpdate {
				break
			}
			if item.Type == "int" {
				if reflect.TypeOf(val).Name() == "string" {
					iVal, e := strconv.Atoi(val.(string))
					if e != nil {
						err = errors.New(fmt.Sprintf("%s需要为数值类型", item.FieldName))
						return
					}
					val = iVal
				}
			} else if item.Type == "interface" {
				jf.InterfaceMap[field] = val
				continue
			}
			if item.Validate != "" {
				errs := ValidateVariable(val, item.Validate)
				if errs != "" {
					item.GetFieldName()
					err = errors.New(fmt.Sprintf("%s%s", item.FieldName, errs))
					return
				}
			}
			fields = append(fields, field)
			asField := strings.Replace(field, "_", "", -1)
			mapValues[asField] = val
		} else {
			if jf.Mode == 0 {
				required := hasRequiredValidate(item.Validate)
				if required {
					item.GetFieldName()
					err = errors.New(fmt.Sprintf("%s为必填字段", item.FieldName))
					return
				}
			}
		}
	}

	if len(fields) > 0 {
		mapstructure.Decode(mapValues, &entity)
	}

	jf.Fields = fields
	return
}

func (jf *JsonForm) AddFields(fieldName ...string) {
	jf.Fields = append(jf.Fields, fieldName...)
}

func (jf *JsonForm) Get(key string) (val interface{}, exists bool) {
	val, exists = jf.Data[key]
	return
}

func (jf *JsonForm) GetInt(key string) (val int, isNumeric bool) {
	v, exists := jf.Get(key)
	if exists {
		switch v.(type) {
		case int64:
			val = int(v.(int64))
			isNumeric = true
		case float64:
			iVal := v.(float64)
			val = int(iVal)
			isNumeric = true
		case string:
			sVal := v.(string)
			var err error
			val, err = strconv.Atoi(sVal)
			if err == nil {
				isNumeric = true
			}
		}
	}
	return
}

func (jf *JsonForm) GetString(key string) (val string, exists bool) {
	v, ok := jf.Get(key)
	if ok {
		exists = true
		switch v.(type) {
		case float64:
			val = strconv.FormatFloat(v.(float64), 'f', -1, 64)
		case string:
			val = v.(string)
		}
	}
	return
}
